1757 - Decimal precision support - #2377
Conversation
|
Also you may check my tests of writing and reading Excel file with decimal lib dependencies https://github.com/ValeryVerkhoturov/excelize-decimal-test |
xuri
left a comment
There was a problem hiding this comment.
Thanks for your pull request, I've left a comments.
xuri
left a comment
There was a problem hiding this comment.
I've reverted changes of decimal-support and align SetCellValue numeric precision with Excel based on your branch.
|
It will work like the Excel GUI pasting now, but I'm still losing precision in the pipeline Decimal DB column → xlsx → Decimal DB column, since the values have to be converted to float along the way. Would it be a good idea to add non-standard behavior option that preserves the exact precision? |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2377 +/- ##
=======================================
Coverage 99.70% 99.70%
=======================================
Files 32 32
Lines 32382 32416 +34
=======================================
+ Hits 32287 32321 +34
Misses 93 93
Partials 2 2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
I think the default expectation when using this library is that it behaves consistently with Excel, so storing values with a precision beyond what Excel supports is unnecessary. However, you can store high-precision values as strings using the |
PR Details
Description
SetCellValuenow recognizes arbitrary precision decimal types and stores themas numbers rather than falling through to
fmt.Sprintand becoming a string.Detection is structural, so no dependency is added. Both of the widely used
decimal packages expose the same two methods on a value receiver, which is
enough to match on:
github.com/shopspring/decimalhasFloat64() (f float64, exact bool)andgithub.com/govalues/decimalhasFloat64() (f float64, ok bool), so bothsatisfy it as-is, as do pointers to them.
The cell is written from
String()rather than fromFloat64(), so the digitsnever pass through a
float64and nothing is rounded on the way out:That is the same shape excelize already emits for a
float64: notattribute, which is the implicit number type.
Covered paths:
SetCellValue, and thereforeSetSheetRow/SetSheetColStreamWriter.SetRow, viasetCellValFuncFallbacks are conservative, so nothing that works today changes:
still renders through the existing
fmt.Sprintpath.String()does not yield somethingisNumericaccepts, the value fallsthrough to the current string path. This matters because
*math/big.Ratalsosatisfies the interface, but its
String()returns"3/2", so it stays astring exactly as it does now.
One behavior change worth calling out explicitly: a type that implements that
method set and was previously stored as a string will now be stored as a
number. That is the point of the change, but it is a visible difference for
anyone who was relying on the old stringly behavior.
Related Issue
#1757
Motivation and Context
Issue #1757 asks for storing decimal precision values natively, for financial
data where a
float64is not an acceptable carrier.Today the only two options both lose something. Passing
decimal.InexactFloat64()produces a number cell but silently rounds once anamount needs more than about 15 significant digits. Passing the decimal object
itself hits the
defaultbranch ofSetCellValue, which stringifies it, so thecell becomes text: Excel will not sum it,
SUMskips it, and the column is nota number at all.
This change gives the third option, a number cell holding the exact digits, with
no dependency on either decimal package.
On precision, and where it does and does not survive
This is worth being precise about, because the two audiences differ.
In the Excel GUI, precision beyond ~15 significant digits is still lost.
Excel stores numbers as IEEE-754 doubles and displays at most 15 significant
digits under the General format, so
1234567890.12345678shows as1234567890.12346, and a workbook opened and re-saved in Excel comes backrounded. No library-side change can avoid that; it is a property of the file
format's consumer.
Read back programmatically, nothing is lost. The exact digits are what land
in the XML, so a reader that asks for the raw value gets the original decimal
back:
GetRowstakes the same option. Note the qualifier: withoutRawCellValuetheGeneral number format is applied on read and the value is rounded to what Excel
would display, so the raw option is required on the read side of a round trip.
That second case is the one that matters most in practice, and it is the
motivating workload here: pipelines that write a workbook, put it in S3, read it
back elsewhere, and load it into a database column typed
DECIMAL. Nothing inthat path opens Excel, so the bytes are never re-rounded, and with this change
the amount that reaches the database is the amount that was written. Before it,
the same pipeline had to choose between a lossy
float64and a text column itthen had to reparse.
How Has This Been Tested
Environment: go1.25.11, darwin/arm64.
TestSetCellValueDecimaladded incell_test.go, covering the value form,the pointer form, a nil pointer, a
*big.Ratnon-regression to confirm anon-numeric
String()still stores as a string, theStreamWriterpath, andthe inline-string overwrite fix.
go test .passes.gofmtandgo vetclean.Verified against the real packages, not only against a mock implementing the
interface. In a separate module depending on
shopspring/decimal v1.4.0andgovalues/decimal v0.1.36with areplaceonto this branch, both produce<c r="A1"><v>...</v></c>with notattribute, identical to thefloat64path, and round-trip tests write a workbook, save it, reopen it with
OpenFileand read it back:
19.991234567890.123456781234567890.12345679007199254740993.019007199254740994The same module pins the read-side behavior described above: raw reads return
the exact digits after a full save and reopen, including through the stream
writer, while a formatted read returns the rounded General-format value.
Types of changes
Checklist